package recommend;

/**
 * 协同过滤推荐算法， Item Base 
 */
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollaborativeFilter {

	public static final int KNEIGHBOUR = 3;// 对与某一个商品需要找到与其相近商品的个数
	public static final int MIN_SCORE = 4; // 最低偏好值(即分数大于等于这个值时认为用户比较认可这个商品)
	public static final double MIN_NEIGHTBOUR_SCORE = 0.5;// 最低相似度值(即相似度大于等于这个值时认为商品比较相似)
	public static final int COLUMNCOUNT = RecommendHelper.goodsCount(); // 商品数量(列)；
	// 训练集的数量必须超过商品数量，否则不能产生相似矩阵
	public static final int TRAINCOUNT = RecommendHelper.userCount() / 2 > COLUMNCOUNT ? RecommendHelper
			.userCount() / 2 : COLUMNCOUNT; // 训练集的数量

	private String[] goodsName = RecommendHelper.goodsName(); // 商品名
	private String[] userName = RecommendHelper.userName(); // 用户名

	private int[][] initDatas = RecommendHelper.getScores();// 数据源

	/**
	 * 该方法用于获得推荐结果，
	 * 
	 * @param testSet
	 *            测试集 ，针对每个用户打分记录推荐
	 */
	public void generateRecommendations(int[][] testSet) {
		printDatas(initDatas, "数据源是");
		// 产生训练集数据
		int[][] trainSet = readTrain();
		printDatas(trainSet, "训练集是");
		// 产生检验集(测试集)(测试集)数据
		printDatas(testSet, "检验集是");
		// 产生相似矩阵
		double[][] similarityMatrix = produceSimilarityMatrix(trainSet);
		printDatas(similarityMatrix, "相似矩阵是");

		Map<Integer, Double> neighborSerial = new HashMap<Integer, Double>();
		for (int i = 0; i < testSet.length; i++) {
			neighborSerial.clear();
			for (int j = 0; j < COLUMNCOUNT; j++) {
				if (testSet[i][j] >= MIN_SCORE) {// 当test[i][j] >=
													// GOODSCORE时说明用户i比较偏好这个商品，那么现在可根据商品间的相似程度为用户推荐其他商品了。
					// 找到与该商品相似程度高的几个商品
					neighborSerial = findKNeighbors(j, similarityMatrix);
					System.out.println(neighborSerial);
					//
					// System.out.println(j + " 为用户 " + userName[TRAINCOUNT + i]
					// + " 推荐的商品是 ： " + goodsName[itemSerial] + " ， 可信度: "
					// + max);
					Set keySet = neighborSerial.keySet();
					for (Iterator iterator = keySet.iterator(); iterator
							.hasNext();) {
						Integer goodsId = (Integer) iterator.next();
						double score = neighborSerial.get(goodsId);

						// 将数据保存到数据库中
						RecommendHelper.writeRecommend(userName[i],
								goodsName[goodsId], score);
					}
				}
			}
		}
	}

	/**
	 * 该方法用于产生与商品i相近的前N个商品序列
	 * 
	 * @param i
	 *            商品i
	 * @param similarityMatrix
	 *            相似矩阵
	 * @return 相似商品的序号和相似度组成的Map
	 */
	private Map<Integer, Double> findKNeighbors(int i,
			double[][] similarityMatrix) {
		// 该方法有三个参数，i表示第i个商品
		Map<Integer, Double> neighborSerial = new HashMap<Integer, Double>();
		double[] similarity = new double[similarityMatrix.length];
		similarity = similarityMatrix[i];
		double[] temp = new double[similarity.length];
		for (int j = 0; j < temp.length; j++) {
			temp[j] = similarity[j];
		}
		// 对相似结果排序
		Arrays.sort(temp);
		for (int m = temp.length - 1; m >= temp.length - KNEIGHBOUR; m--) {
			for (int j = 0; j < similarity.length; j++) {
				if (similarity[j] == temp[m]
						&& similarity[j] > MIN_NEIGHTBOUR_SCORE)
					if (!neighborSerial.containsKey(j)) {
						neighborSerial.put(j, similarity[j]);
					}
			}
		}
		return neighborSerial;
	}

	/**
	 * 该方法通过训练集产生相似矩阵。
	 * 
	 * @param trainSet
	 *            训练集
	 * @return 相似矩阵
	 */
	private double[][] produceSimilarityMatrix(int[][] trainSet) {
		double[][] similarityMatrix = new double[COLUMNCOUNT][COLUMNCOUNT];
		for (int i = 0; i < COLUMNCOUNT; i++) {
			for (int j = 0; j < COLUMNCOUNT; j++) {
				if (i == j) {
					// 商品本身的相似度，记为0，以免推荐自身
					similarityMatrix[i][j] = 0;
				} else {
					// 计算商品的相似度
					similarityMatrix[i][j] = computeSimilarity(
							getItemScores(trainSet, i),
							getItemScores(trainSet, j));
				}
			}
		}

		return similarityMatrix;
	}

	/**
	 * 该方法用于获得训练集中所有用户对商品n的评分
	 * 
	 * @param scores
	 *            二维数组，原始数据，训练集中用户对商品的打分
	 * @param n
	 *            商品序号
	 * @return 训练集中所有用户对商品n的评分
	 */
	private int[] getItemScores(int[][] scores, int n) {
		int[] score = new int[scores.length];
		for (int i = 0; i < scores.length; i++) {
			score[i] = scores[i][n];
		}
		return score;
	}

	/**
	 * 该方法用来计算两个商品的相似度
	 * 
	 * @param item1
	 *            用户i对各个商品的打分
	 * @param item2
	 *            用户j对各个商品的打分
	 * @return 两个商品的相似度
	 */
	private double computeSimilarity(int[] item1, int[] item2) {
		List<Integer> list1 = new ArrayList<Integer>();
		List<Integer> list2 = new ArrayList<Integer>();
		for (int i = 0; i < item1.length; i++) {
			if (item1[i] != 0 && item2[i] != 0) {
				/*
				 * 在协同过滤算法中，针对商品item1和item2，如果一个人只对其中的一个商品打分，那么这个人的打分记录是不能参与相似度计算的
				 * 。因为该人是没购买该商品，你不能认为他对该商品的评价为0.
				 */
				list1.add(new Integer(item1[i]));
				list2.add(new Integer(item2[i]));
			}
		}
		return pearsonCorrelation(list1, list2);
	}

	/**
	 * 
	 * 该方法用来从数据库中得到训练集
	 * 
	 * @param rowCount
	 *            //获得多少行
	 * @return
	 */
	private int[][] readTrain() {
		int[][] datas = new int[TRAINCOUNT][COLUMNCOUNT];

		// 产生训练集
		for (int i = 0; i < TRAINCOUNT; i++) {
			for (int j = 0; j < COLUMNCOUNT; j++) {
				datas[i][j] = initDatas[i][j];
			}
		}

		return datas;
	}

	/**
	 * 该方法用皮尔逊积聚系数（皮尔逊相关系数）计算两个商品的相似度
	 * 
	 * @param a
	 *            a商品的打分情况
	 * @param b
	 *            b商品的打分情况
	 * @return 相关系数
	 */
	private double pearsonCorrelation(List<Integer> a, List<Integer> b) {
		int num = a.size();
		int sum_prefOne = 0;
		int sum_prefTwo = 0;
		int sum_squareOne = 0;
		int sum_squareTwo = 0;
		int sum_product = 0;
		for (int i = 0; i < num; i++) {
			sum_prefOne += a.get(i);
			sum_prefTwo += b.get(i);
			sum_squareOne += Math.pow(a.get(i), 2);
			sum_squareTwo += Math.pow(b.get(i), 2);
			sum_product += a.get(i) * b.get(i);
		}
		double sum = num * sum_product - sum_prefOne * sum_prefTwo;
		double den = Math.sqrt((num * sum_squareOne - Math.pow(sum_prefOne, 2))
				* (num * sum_squareTwo - Math.pow(sum_prefTwo, 2)));
		// double result = sum / den;
		return den == 0 ? 0.0 : sum / den;
	}

	// 输出数据
	private void printDatas(int[][] datas, String msg) {
		System.out.println("====================================" + msg
				+ "====================================");
		int index = 0;
		for (int[] row : datas) {
			for (int score : row) {
				System.out.print(score + "--");
			}
			index++;
			System.out.println("===>" + index);
		}
	}

	// 输出数据
	private void printDatas(double[][] datas, String msg) {
		System.out.println("====================================" + msg
				+ "====================================");
		int index = 0;
		for (double[] row : datas) {
			for (double score : row) {
				System.out.printf("%.2f\t", score);
			}
			index++;
			System.out.println("===>" + index);
		}
	}

	public static void main(String[] args) {

		CollaborativeFilter recommend = new CollaborativeFilter();
		recommend.generateRecommendations(RecommendHelper.getScores());
		int i = 0;
		for (int[] row : RecommendHelper.getScores()) {
			for (int data : row) {
				if (data > MIN_SCORE) {
					i++;
				}
			}
		}
		System.out.println("======" + i);

	}
}
